using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Media.Media3D;

using NewtonDynamics;
using System.Diagnostics;

namespace WPFPhysics1
{
	public enum BodyType { Rectangle, Ellipse, Polygon, Car };

	public class Bodies : ObservableCollection<CBody>
	{
		private const double depth = 5f;

		private CWorld world = new CWorld();
		private DateTime? lastUpdate;
		private Collection<CBody> removed = new Collection<CBody>();
		private VehicleBody car;

		protected double worldWidth;
		protected double worldHeight;

		public Bodies( double worldWidth, double worldHeight )
		{
			this.worldWidth = worldWidth;
			this.worldHeight = worldHeight;

			world.SetSize( new Vector3D( -worldWidth / 2, -worldHeight / 2, -2 ), new Vector3D( worldWidth / 2, worldHeight / 2, 2 ) );
/*			world.SetFrictionModel( EFrictionModel.ExactModel );
			world.SetSolverModel( ESolverModel.ExactMode );*/

			world.BodyLeaveWorld += new EventHandler<CBodyLeaveWorldEventArgs>( world_BodyLeaveWorld );
		}

		/// <summary>
		/// Body left world.  Remove from ObservableCollection and leave for GC.
		/// </summary>
		/// <param name="sender">World</param>
		/// <param name="e">Event arguments</param>
		private void world_BodyLeaveWorld( object sender, CBodyLeaveWorldEventArgs e )
		{
			if ( e.Body != null )
			{
				if ( car == e.Body ) car = null;
				Remove( e.Body );
			}
		}

		public void SetPlatforms()
		{
			Collection<Body> platforms = new Collection<Body>();

			foreach ( Body body in this ) if ( body is StaticRectangleBody ) platforms.Add( body );
			foreach ( StaticRectangleBody platform in platforms ) Remove( platform );

			Random rand = new Random();

			AddRectangle( -2, -1, 7, 0.15f, 15, true );

			for ( int ledge = 0; ledge < 10; ++ledge )
			{
				AddRectangle(
					( -4.5 + ( rand.NextDouble() * 9 ) ),
					( -2 + ( rand.NextDouble() * 6 ) ),
					( 1 + ( rand.NextDouble() * 3 ) ),
					0.15f,
					( rand.NextDouble() * 60 ) - 30,
					true );
			}
		}

		/// <summary>
		/// Drop objects
		/// </summary>
		public void DropObjects( BodyType type )
		{
			Random rand = new Random();
			int maxItems = 5;

			for ( int index = 0; index < maxItems; ++index )
			{
				double x = -4.5 + ( 9 * ( (double)index / maxItems ) );
				double y = -4;

				switch ( type )
				{
					case BodyType.Rectangle:
						AddRectangle( x, y, 0.40, 0.40, rand.NextDouble() * 90f, false );
						break;

					case BodyType.Ellipse:
						AddEllipse( x, y, 0.40, 0.35, rand.NextDouble() * 90f );
						break;

					case BodyType.Polygon:
						AddPolygon( x, y, new Vector[] { new Vector( 0, 0.20 ), new Vector( -0.20, -0.20 ), new Vector( 0.20, -0.20 ) } );
						break;
				}
			}

			if ( type == BodyType.Car )
			{
				if ( car != null ) Remove( car );

				car = AddCar( ( rand.NextDouble() * 5 ) - 4, -4.5, 2, 0.75 );
			}
		}

		/// <summary>
		/// Add rectangle
		/// </summary>
		/// <param name="x">X offset</param>
		/// <param name="y">Y offset</param>
		/// <param name="width">Width</param>
		/// <param name="height">Height</param>
		/// <param name="angle">Angle (degrees)</param>
		/// <param name="isStatic">Static?</param>
		public void AddRectangle( double x, double y, double width, double height, double angle, bool isStatic )
		{
			RectangleBody body = RectangleBody.Create(
				world,
				isStatic ? double.PositiveInfinity : 1,
				width,
				height,
				isStatic ? depth * 2 : depth );

			Matrix3D matrix = Matrix3D.Identity;

			// Rotate around the Z axis
			matrix.Rotate( new Quaternion( new Vector3D( 0, 0, 1 ), angle ) );

			// Translate to x, y
			matrix.Translate( new Vector3D( x, y, 0 ) );

			// Apply matrix
			body.Matrix = matrix;

			if ( !isStatic )
			{
				body.ApplyForceAndTorque += new EventHandler<CApplyForceAndTorqueEventArgs>( body_ApplyForceAndTorque );
			}

			Add( body );
		}

		/// <summary>
		/// Add ellipse
		/// </summary>
		/// <param name="x">X offset</param>
		/// <param name="y">Y offset</param>
		/// <param name="width">Width</param>
		/// <param name="height">Height</param>
		/// <param name="angle">Angle (radians)</param>
		public void AddEllipse( double x, double y, double width, double height, double angle )
		{
			EllipseBody body = EllipseBody.Create(
				world,
				1f,
				width,
				height,
				depth );

			Matrix3D matrix = Matrix3D.Identity;

			// Rotate around the Z axis
			matrix.Rotate( new Quaternion( new Vector3D( 0, 0, 1 ), angle ) );

			// Translate to x, y
			matrix.Translate( new Vector3D( x, y, 0 ) );

			// Apply matrix
			body.Matrix = matrix;

			body.ApplyForceAndTorque += new EventHandler<CApplyForceAndTorqueEventArgs>( body_ApplyForceAndTorque );

			Add( body );
		}

		/// <summary>
		/// Add polygon
		/// </summary>
		/// <param name="x">X offset</param>
		/// <param name="y">Y offset</param>
		/// <param name="width">Width</param>
		/// <param name="height">Height</param>
		/// <param name="vertices">Array of vertices</param>
		public void AddPolygon( double x, double y, Vector[] vertices )
		{
			PolygonBody body = PolygonBody.Create(
				world,
				1f,
				vertices,
				depth );

			Matrix3D matrix = Matrix3D.Identity;

			// Translate to x, y
			matrix.Translate( new Vector3D( x, y, 0 ) );

			// Apply matrix
			body.Matrix = matrix;

			body.ApplyForceAndTorque += new EventHandler<CApplyForceAndTorqueEventArgs>( body_ApplyForceAndTorque );

			Add( body );
		}

		/// <summary>
		/// Add car
		/// </summary>
		/// <param name="x">X offset</param>
		/// <param name="y">Y offset</param>
		/// <param name="width">Width</param>
		/// <param name="height">Height</param>
		/// <returns>Car body</returns>
		public VehicleBody AddCar( double x, double y, double width, double height )
		{
			// Work in progress - Newton has some basic vehicle modeling built in (suspension etc).  Would be nice to use this (at least to play with).
			// In practice will probably just figure how to attach static axles to a body, a ball joint on the end of each (constrained to the z-axis)
			// and circles or polygons as tires (polygons would be nice 'hand drawn' so the car can roll on lumpy tires like the YouTube vid).

			VehicleBody body = VehicleBody.Create(
				world,
				1500f,
				width,
				height,
				depth );

			Matrix3D matrix = Matrix3D.Identity;

			// Translate to x, y
			matrix.Translate( new Vector3D( x, y, 0f ) );

			// Apply transform
			body.Matrix = matrix;

			body.ApplyForceAndTorque += new EventHandler<CApplyForceAndTorqueEventArgs>( body_ApplyForceAndTorque );

			Add( body );

			return body;
		}

		/// <summary>
		/// Apply force and torque callback
		/// </summary>
		/// <param name="sender">Body</param>
		/// <param name="e">Event arguments</param>
		void body_ApplyForceAndTorque( object sender, CApplyForceAndTorqueEventArgs e )
		{
			CBody body = (CBody)sender;
			body.AddForce( new Vector3D( 0, 9.8f * body.MassMatrix.m_Mass, 0 ) );
		}

		/// <summary>
		/// Remove item
		/// </summary>
		/// <param name="body">Body to remove</param>
		private new void Remove( CBody body )
		{
			if ( !removed.Contains( body ) )
			{
				base.Remove( body );
				removed.Add( body );
			}
		}

		/// <summary>
		/// Update world
		/// </summary>
		public void Update()
		{
			DateTime time = DateTime.Now;

			world.Update( lastUpdate.HasValue ? (float)( time - lastUpdate.Value ).TotalSeconds : 0 );

			if ( removed.Count > 0 )
			{
				foreach ( CBody body in removed ) body.Dispose();
				removed.Clear();
			}

			lastUpdate = time;
		}

		/// <summary>
		/// Get world width
		/// </summary>
		public double WorldWidth { get { return worldWidth; } }

		/// <summary>
		/// Get world height
		/// </summary>
		public double WorldHeight { get { return worldHeight; } }
	}
}